#include "ShaderLoader.h"

#include <glad/glad.h>
#include <SEDebug/Log/Log.h>
#include <fstream>
#include <sstream>
#include <SERender/Resourses/UniformBuffer.h>
#include <SECore/ResourcePayload.h>
#include <SERender/Resourses/Loader/TextureLoader.h>

namespace SEngine
{

    ShaderLoader & ShaderLoader::Load(const std::string &fileName)
    {
        // m_name = fileName;
        std::ifstream file(fileName);
        if (file.is_open()) {
            std::stringstream ss;
            ss << file.rdbuf();
            std::string src(std::move(ss.str()));
            if (src.find("// VS_BEGIN") != std::string::npos && src.find("// VS_END") != std::string::npos)
                m_vs = src.substr(src.find("// VS_BEGIN"), src.find("// VS_END") - src.find("// VS_BEGIN"));
            if (src.find("// GS_BEGIN") != std::string::npos && src.find("// GS_END") != std::string::npos)
                m_gs = src.substr(src.find("// GS_BEGIN"), src.find("// GS_END") - src.find("// GS_BEGIN"));
            if (src.find("// FS_BEGIN") != std::string::npos && src.find("// FS_END") != std::string::npos)
                m_fs = src.substr(src.find("// FS_BEGIN"), src.find("// FS_END") - src.find("// FS_BEGIN"));
            file.close();
        } else {
            Log::GetInstance().Error("{} is not exist", fileName);
        }
        return *this;
    }

    void CompliteShader(GLuint id, const std::string src) {
        const char * p = src.data();
        glShaderSource(id, 1, &p, nullptr);
        glCompileShader(id);
        int  success;
        char infoLog[512];
        glGetShaderiv(id, GL_COMPILE_STATUS, &success);
        if(!success) {      
            glGetShaderInfoLog(id, 512, NULL, infoLog);
            Log::GetInstance().Error("{}", infoLog);
        }
    }

    template<>
    std::unique_ptr<Shader> ShaderLoader::CreateShader<Shader>(UniformBlockBinder& binder)
    {
        auto shader = std::make_unique<Shader>();
        CreateShaderImpl(*shader);
        binder.Bind(*shader);
        return shader;
    }

    void ShaderLoader::LoadUniforms(Shader & shader)
    {
        shader.m_uniformInfos.Clear();
        GLint nums;
        glGetProgramiv(shader.GetId(), GL_ACTIVE_UNIFORMS, &nums);
        GLchar name[256] = {0};
        int samplerIndex = 0;
        GLuint * indices = (GLuint*)alloca(sizeof(GLuint) * nums);
        for (int i = 0; i < nums; ++i) { indices[i] = i; }
        GLint * params = (GLint*)alloca(sizeof(GLint) * nums);
        glGetActiveUniformsiv(shader.GetId(), nums, indices, GL_UNIFORM_BLOCK_INDEX, params);
        for (int i = 0; i < nums; ++i) {
            if (params[i] != -1) { // Remove UniformBlock 
                continue;
            }
            GLsizei length = 0;
            GLint size = 0;
            GLenum type;
            glGetActiveUniform(shader.GetId(), i, sizeof(name), &length, &size, &type, name);
            std::string uniformName = std::string(name, length);
            uniformName = uniformName.substr(0, uniformName.find_first_of('['));
            for (int j = 0; j < size; j++) {
                std::string name_;
                if (size != 1) {
                    name_ = uniformName + "[" + std::to_string(j) + "]";
                } else {
                    name_ = uniformName;
                }
                switch (type)
                {
                case GL_INT:            shader.m_uniformInfos.EmplaceBack(name_, 0); break;             
                case GL_FLOAT:          shader.m_uniformInfos.EmplaceBack(name_, 1.0f); break;             
                case GL_FLOAT_VEC2:     shader.m_uniformInfos.EmplaceBack(name_, glm::vec2(1.0f)); break;        
                case GL_FLOAT_VEC3:     shader.m_uniformInfos.EmplaceBack(name_, glm::vec3(1.0f)); break;        
                case GL_FLOAT_VEC4:     shader.m_uniformInfos.EmplaceBack(name_, glm::vec4(1.0f)); break;        
                case GL_FLOAT_MAT3:     shader.m_uniformInfos.EmplaceBack(name_, glm::mat3(1.0f)); break;        
                case GL_FLOAT_MAT4:     shader.m_uniformInfos.EmplaceBack(name_, glm::mat4(1.0f)); break;        
                case GL_SAMPLER_2D:     shader.m_uniformInfos.EmplaceBack(name_, Shader::Sampler{*Texture2D::GetDefaultTexture(), (TextureSlot)samplerIndex++}); break;             
                case GL_SAMPLER_CUBE:   shader.m_uniformInfos.EmplaceBack(name_, Shader::Sampler{*TextureCube::GetDefaultTexture(), (TextureSlot)samplerIndex++}); break;             
                case GL_IMAGE_1D:       break;
                case GL_IMAGE_2D:       break;
                case GL_IMAGE_3D:       break;
                default:                Log::GetInstance().Warn("Not supported Type"); break;
                }
            }
        }
    }

    void ShaderLoader::LoadUniformBlocks(Shader &shader)
    {
        GLint nums;
        glGetProgramiv(shader.GetId(), GL_ACTIVE_UNIFORM_BLOCKS, &nums);
        GLchar name[256] = {0};
        for (int i = 0; i < nums; ++i) {
            GLint activeUniformNum;
            glGetActiveUniformBlockiv(shader.GetId(), i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &activeUniformNum);
            GLint * indices = (GLint*)alloca(sizeof(GLint) * activeUniformNum);
            glGetActiveUniformBlockiv(shader.GetId(), i, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices);
            for (int j = 0; j < activeUniformNum; ++j) {
                GLsizei length = 0;
                GLint size = 0;
                GLenum type;
                glGetActiveUniform(shader.GetId(), j, sizeof(name), &length, &size, &type, name);
            }
        }
    }

    void ShaderLoader::CreateShaderImpl(Shader &shader)
    {
        auto vs = glCreateShader(GL_VERTEX_SHADER);
        auto gs = glCreateShader(GL_GEOMETRY_SHADER);
        auto fs = glCreateShader(GL_FRAGMENT_SHADER);
        if (!m_vs.empty()) {
            glCall(CompliteShader(vs, m_vs));
            glAttachShader(shader.GetId(), vs);
        }
        if (!m_gs.empty()) {
            glCall(CompliteShader(gs, m_gs));
            glAttachShader(shader.GetId(), gs);
        }
        if (!m_fs.empty()) {
            glCall(CompliteShader(fs, m_fs));
            glAttachShader(shader.GetId(), fs);
        }  
        glLinkProgram(shader.GetId());
        glDeleteShader(vs);
        glDeleteShader(gs);
        glDeleteShader(fs);
        LoadUniforms(shader);
    }

} // namespace SEngine
